1
Transição para Gráficos de Alta Performance
AI020Lesson 8
00:00

Em gráficos computacionais, distinguimos entre Vetorial e Mapa de Bits gráficos. Gráficos vetoriais (como SVG) descrevem imagens por meio de formas lógicas; cada elemento é um objeto persistente no DOM. Por outro lado, gráficos de mapa de bits (como HTML5 Canvas) trabalham com matrizes de pontos coloridos.

1. A Transição para Canvas

Embora o SVG seja mais fácil de estilizar com CSS, o navegador precisa rastrear cada nó. Para necessidades de alto desempenho, como jogos com milhares de partes móveis, a API Canvas é superior. Ela oferece um único elemento DOM que encapsula uma superfície de desenho — essencialmente uma "tela em branco".

2. O Contexto de Desenho

O <canvas> elemento é uma "caixa preta" até inicializarmos seu contexto. Os métodos deste objeto fornecem a interface real de desenho, desacoplando o elemento de exibição da lógica de renderização.

var contexto = canvas.getContext("2d");

3. Consciência de Espaço de Nomes

Em gráficos baseados em XML como SVG, o xmlns="http://www.w3.org/2000/svg" atributo é crítico. Ele sinaliza ao navegador para mudar da análise padrão de HTML para o esquema gráfico específico, permitindo que as tags de formas sejam reconhecidas como objetos interativos.

main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
", "execution_steps": [ { "output": "Rendering SVG: Cyan circle (updated from red) and blue outlined square." }, { "output": "Rendering Canvas: Solid red rectangle drawn via pixel raster." } ] }; const executionSteps = (courseData && courseData.execution_steps) || []; // ── Machine Translation Data Hydration ── const domCode = document.getElementById('data-course-code'); if (domCode && courseData) { courseData.code = domCode.textContent; } const domExecs = document.querySelectorAll('#data-exec-output .data-exec'); domExecs.forEach(el => { const stepIdx = parseInt(el.getAttribute('data-step'), 10); if (!isNaN(stepIdx) && executionSteps[stepIdx]) { executionSteps[stepIdx].output = el.textContent.trim(); } }); const pageNumber = '1'; let visualMode = 'code'; let studyTimerInterval = null; let studyElapsedTime = 0; let studyLastStartTime = 0; let isStudyTimerRunning = false; let isSimRunning = false; let currentStep = 0; let timer = null; let animFrameId; let startTime = 0; // Global State let masterTimeline = null; let hybridInitialized = false; let totalTimelineSteps = 0; // tracks how many step labels the fallback timeline defines const els = { codeContainer: document.getElementById('code-container'), quizContainer: document.getElementById('quiz-container'), examContainer: document.getElementById('exam-container'), tabCode: document.getElementById('tab-code'), tabSim: document.getElementById('tab-sim'), tabQuiz: document.getElementById('tab-quiz'), tabExam: document.getElementById('tab-exam'), visControls: document.getElementById('vis-controls'), visStatusBar: document.getElementById('vis-status-bar'), code: document.getElementById('code-content'), visContainer: document.querySelector('.vis-container'), tipsBtn: document.getElementById('tips-btn'), drawer: document.getElementById('drawer-overlay'), timerDisplay: document.getElementById('study-timer'), timerVal: document.getElementById('timer-val'), consoleOutput: document.getElementById('console-output'), runBtn: document.getElementById('run-btn') }; // --- HELPERS --- /** * [通用渲染方法] General LaTeX Render Method * 封装 MathJax 渲染逻辑,可被多次调用 * @param {HTMLElement | Array} elements - 可选,指定渲染的元素或元素数组 */ function renderLaTeX(elements) { if (window.MathJax && typeof window.MathJax.typesetPromise === 'function') { // 如果传入了具体元素,只渲染这些元素 if (elements) { //确保 elements 是数组形式 const elArray = Array.isArray(elements) ? elements : [elements]; MathJax.typesetPromise(elArray).catch(err => console.warn('MathJax specific render error:', err)); } else { // 否则渲染全页 MathJax.typesetPromise().catch(err => console.warn('MathJax global render error:', err)); } } } // Syntax Highlighter adapted for Python keywords function renderCode(codeStr) { return codeStr.split('\n').map((line, i) => { const lineNum = i + 1; let htmlLine = ''; const regex = /((?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'))|(#.*)|(\b(?:def|class|if|else|elif|while|for|in|return|import|from|as|try|except|finally|with|lambda|pass|break|continue)\b)|(\b(?:print|len|range|enumerate|zip|map|filter|set|list|dict|int|str|float|sum|max|min|append|pop)\b)|(\b(?:True|False|None|[0-9]+)\b)|(\+|-|\*|\/|=|<|>|!|%|\[|\]|\{|\}|\(|\))/g; let lastIndex = 0; let match; while ((match = regex.exec(line)) !== null) { const textBefore = line.slice(lastIndex, match.index); htmlLine += escapeHtml(textBefore); const [fullMatch, str, com, kw, fn, num, op] = match; if (str) htmlLine += `${escapeHtml(str)}`; else if (com) htmlLine += `${escapeHtml(com)}`; else if (kw) htmlLine += `${escapeHtml(kw)}`; else if (fn) htmlLine += `${escapeHtml(fn)}`; else if (num) htmlLine += `${escapeHtml(num)}`; else if (op) htmlLine += `${escapeHtml(op)}`; lastIndex = regex.lastIndex; } htmlLine += escapeHtml(line.slice(lastIndex)); return `
${lineNum}
${htmlLine}
`; }).join(''); } function escapeHtml(text) { if (!text) return ''; return text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } // Helper to clear all highlights // Helper to clear all highlights function clearHighlights() { const lines = document.querySelectorAll('.code-line'); lines.forEach(l => l.classList.remove('executing')); } // --- INTERACTION LOGIC --- // NEW: Advanced Simulation with Line Highlighting window.runSimulatedCode = async function () { if (isSimRunning) return; const term = els.consoleOutput; if (!term) return; isSimRunning = true; if (els.runBtn) els.runBtn.disabled = true; try { // Initial Term State term.innerHTML = `
${courseData.run_cmd || '> python3 main.py'}
`; const wait = (ms) => new Promise(r => setTimeout(r, ms)); await wait(400); // Print all outputs at once (no line-by-line highlight) for (let i = 0; i < executionSteps.length; i++) { const step = executionSteps[i]; if (step.output) { const div = document.createElement('div'); div.className = 'console-line'; div.innerText = step.output; term.appendChild(div); } } // Finish const cursor = document.createElement('div'); cursor.className = 'console-line'; cursor.innerHTML = `> `; term.appendChild(cursor); term.scrollTop = term.scrollHeight; } catch (err) { console.error("Simulation error", err); } finally { isSimRunning = false; if (els.runBtn) els.runBtn.disabled = false; } }; // Copy Code Function window.copyCode = function () { if (navigator.clipboard) { navigator.clipboard.writeText(courseData.code).then(() => { const btn = document.querySelector('.ide-btn[onclick="copyCode()"]'); if (btn) { const parent = btn.parentElement; const originalHtml = parent.innerHTML; parent.innerHTML = ` Copied!`; lucide.createIcons(); setTimeout(() => { parent.innerHTML = originalHtml; lucide.createIcons(); }, 2000); } }); } }; // Challenge/Exam Toggle // Quiz Selection Logic function toggleFullScreen() { const elem = els.visContainer; if (elem) { if (!document.fullscreenElement) { elem.requestFullscreen().catch(err => { console.error(err); }); } else { document.exitFullscreen(); } } } const fsBtn = document.getElementById('fsBtn'); if (fsBtn) { document.addEventListener('fullscreenchange', () => { const isFull = !!document.fullscreenElement; fsBtn.innerHTML = isFull ? '' : ''; if (window.lucide) lucide.createIcons(); }); } // Quiz answer handler (reference-style from InnerPage General) window.checkAnswer = function(card, prefix, isCorrect) { const grid = card.closest('.quiz-options-grid'); if (grid.classList.contains('answered')) return; grid.classList.add('answered'); card.classList.add('selected', isCorrect ? 'correct' : 'incorrect'); const icon = document.createElement('i'); icon.setAttribute('data-lucide', isCorrect ? 'check-circle' : 'x-circle'); icon.style.flexShrink = '0'; card.appendChild(icon); if (window.lucide) lucide.createIcons(); const correctBox = document.getElementById(prefix + '-correct'); const incorrectBox = document.getElementById(prefix + '-incorrect'); if (isCorrect && correctBox) { correctBox.style.display = 'block'; } else if (!isCorrect && incorrectBox) { incorrectBox.style.display = 'block'; } }; // Exam solution toggle (reference-style from InnerPage General) window.toggleExamSolution = function(btn) { const ansDiv = btn.nextElementSibling; const isVis = ansDiv.classList.toggle('visible'); btn.innerHTML = isVis ? 'Hide Solution ' : 'Show Solution '; if (window.lucide) lucide.createIcons(); if (isVis) renderLaTeX(ansDiv); }; // --- APP LOGIC --- // Quiz card reset window.resetQuizCard = function(btn) { const card = btn.closest('.quiz-card'); if (!card) return; const grid = card.querySelector('.quiz-options-grid'); if (grid) { grid.classList.remove('answered'); grid.querySelectorAll('.quiz-opt-card').forEach(opt => { opt.classList.remove('selected', 'correct', 'incorrect'); const icon = opt.querySelector('i'); if (icon) icon.remove(); }); } card.querySelectorAll('.quiz-feedback-box').forEach(fb => { fb.style.display = 'none'; fb.classList.remove('visible'); }); }; function init() { try { if (courseData.filename && document.getElementById('code-filename-display')) { document.getElementById('code-filename-display').textContent = courseData.filename; } loadContent(); } catch (e) { console.error("Content loading failed", e); } try { initStudyTimer(); } catch (e) { console.error("Timer init failed", e); } if (courseData.visual && courseData.visual.simStructure && !courseData.visual.simSteps) { courseData.visual.simSteps = []; } if (els.tabCode) els.tabCode.onclick = () => setVisualMode('code'); if (els.tabSim) els.tabSim.onclick = () => setVisualMode('sim'); if (els.tabQuiz) els.tabQuiz.onclick = () => setVisualMode('quiz'); if (els.tabExam) els.tabExam.onclick = () => setVisualMode('exam'); if (document.getElementById('btn-play') && typeof togglePlay === 'function') document.getElementById('btn-play').onclick = togglePlay; if (document.getElementById('btn-next') && typeof step === 'function') document.getElementById('btn-next').onclick = () => step(1); if (document.getElementById('btn-prev') && typeof step === 'function') document.getElementById('btn-prev').onclick = () => step(-1); if (document.getElementById('btn-reset') && typeof resetSim === 'function') document.getElementById('btn-reset').onclick = resetSim; if (els.tipsBtn) els.tipsBtn.onclick = () => els.drawer.classList.add('active'); const closeDrawer = document.getElementById('close-drawer'); if (closeDrawer) closeDrawer.onclick = () => els.drawer.classList.remove('active'); if (els.drawer) els.drawer.onclick = (e) => { if (e.target === els.drawer) els.drawer.classList.remove('active'); }; } function initStudyTimer() { startStudyTimer(); if (els.timerDisplay) els.timerDisplay.onclick = toggleStudyTimer; } function toggleStudyTimer() { if (isStudyTimerRunning) { pauseStudyTimer(); } else { startStudyTimer(); } } function startStudyTimer() { if (isStudyTimerRunning) return; isStudyTimerRunning = true; studyLastStartTime = Date.now(); if (els.timerDisplay) els.timerDisplay.classList.remove('paused'); updateTimerDisplay(); studyTimerInterval = setInterval(updateTimerDisplay, 1000); } function pauseStudyTimer() { if (!isStudyTimerRunning) return; isStudyTimerRunning = false; studyElapsedTime += Date.now() - studyLastStartTime; clearInterval(studyTimerInterval); if (els.timerDisplay) els.timerDisplay.classList.add('paused'); updateTimerDisplay(); } function updateTimerDisplay() { let totalMs = studyElapsedTime; if (isStudyTimerRunning) totalMs += (Date.now() - studyLastStartTime); const totalSecs = Math.floor(totalMs / 1000); const m = Math.floor(totalSecs / 60).toString().padStart(2, '0'); const s = (totalSecs % 60).toString().padStart(2, '0'); if (els.timerVal) els.timerVal.innerText = `${m}:${s}`; } function loadContent() { renderLaTeX(); if (courseData.code && els.code) els.code.innerHTML = renderCode(courseData.code); setVisualMode(visualMode); } function setVisualMode(mode) { visualMode = mode; if (els.tabCode) els.tabCode.classList.toggle('active', mode === 'code'); if (els.tabSim) els.tabSim.classList.toggle('active', mode === 'sim'); if (els.tabQuiz) els.tabQuiz.classList.toggle('active', mode === 'quiz'); if (els.tabExam) els.tabExam.classList.toggle('active', mode === 'exam'); if (els.codeContainer) els.codeContainer.style.display = 'none'; if (els.quizContainer) els.quizContainer.style.display = 'none'; if (els.examContainer) els.examContainer.style.display = 'none'; if (els.canvas) els.canvas.style.display = 'none'; if (els.visControls) els.visControls.style.display = 'none'; if (els.visStatusBar) els.visStatusBar.style.display = 'none'; if (mode === 'code') { if (els.codeContainer) els.codeContainer.style.display = 'flex'; } else if (mode === 'quiz') { if (els.quizContainer) { els.quizContainer.style.display = 'block'; renderLaTeX(els.quizContainer); } } else if (mode === 'exam') { if (els.examContainer) { els.examContainer.style.display = 'block'; renderLaTeX(els.examContainer); } } } init(); window.addEventListener('resize', () => {});